package handlers

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"smartposts/internal/global"
	"smartposts/internal/models"
	"smartposts/internal/mq"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type VoteInput struct {
	VoteType int `json:"vote_type" binding:"required,oneof=-1 1"`
}

func VotePost(c *gin.Context) {
	var input VoteInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	postID := c.Param("id")
	username, _ := c.Get("username")

	var user models.User
	if result := global.Db.Where("username = ?", username).First(&user); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var post models.Post
	if result := global.Db.First(&post, postID); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"})
		return
	}

	var existingVote models.Vote
	result := global.Db.Where("user_id = ? AND post_id = ?", user.ID, post.ID).First(&existingVote)

	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			// New vote
			vote := models.Vote{
				UserID:   user.ID,
				PostID:   post.ID,
				VoteType: input.VoteType,
			}
			if err := global.Db.Create(&vote).Error; err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create vote"})
				return
			}

			if input.VoteType == models.VoteTypeUp {
				post.UpvoteCount++
			} else {
				post.DownvoteCount++
			}
			post.VoteCount = post.UpvoteCount - post.DownvoteCount
			if err := global.Db.Save(&post).Error; err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update post vote count"})
				return
			}

			// Publish a message to the worker to update Redis
			body, err := json.Marshal(post)
			if err != nil {
				// Log the error, but don't fail the request since the post was created.
				// The cache can be rebuilt later.
				// log.Printf("Failed to marshal post ID for queue: %v", err)
				c.JSON(http.StatusCreated, post)
				return
			}

			if err := mq.MQ.Publish("rank.update", body); err != nil {
				// Log the error, but don't fail the request.
				// log.Printf("Failed to publish post creation message: %v", err)
			}

			c.JSON(http.StatusCreated, gin.H{"message": "Vote created successfully"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}

	// Existing vote
	if existingVote.VoteType == input.VoteType {
		// Same vote, so un-vote
		if err := global.Db.Delete(&existingVote).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to remove vote"})
			return
		}

		if input.VoteType == models.VoteTypeUp {
			post.UpvoteCount--
		} else {
			post.DownvoteCount--
		}
		post.VoteCount = post.UpvoteCount - post.DownvoteCount
	} else {
		// Different vote, update vote
		existingVote.VoteType = input.VoteType
		if err := global.Db.Save(&existingVote).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update vote"})
			return
		}

		if input.VoteType == models.VoteTypeUp {
			post.UpvoteCount++
			post.DownvoteCount--
		} else {
			post.UpvoteCount--
			post.DownvoteCount++
		}
		post.VoteCount = post.UpvoteCount - post.DownvoteCount
	}

	if err := global.Db.Save(&post).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update post vote count"})
		return
	}

	// Publish a message to the worker to update Redis
	body, err := json.Marshal(post)
	if err != nil {
		// Log the error, but don't fail the request since the post was created.
		// The cache can be rebuilt later.
		log.Printf("Failed to marshal post ID for queue: %v", err)
		c.JSON(http.StatusCreated, post)
		return
	}

	if err := mq.MQ.Publish("rank.update", body); err != nil {
		// Log the error, but don't fail the request.
		log.Printf("Failed to publish post creation message: %v", err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Vote updated successfully"})
}

func GetUserVotes(c *gin.Context) {
	username, _ := c.Get("username")
	var user models.User
	if result := global.Db.Where("username = ?", username).First(&user); result.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}
	var votes []models.Vote
	if result := global.Db.Where("user_id = ?", user.ID).Find(&votes); result.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()})
		return
	}
	c.JSON(http.StatusOK, votes)
}
